"""
Posnet server module.
contains the class  "posnet_server"
"""
import sys
import posnet_tcp_server
import threading,thread,time,socket
import logging
from traceback import print_exc
import thread
#import cProfile, pstats

import posnet_overlay_messages
import posnet_tcp_client
import posnet_overlay_node2
add_to_str = lambda o : "%s %s" % (o[0],o[1])

class internet_posnet_underlay:
	def __init__(self):
		self.logger = logging.getLogger('posnet.underlay')
		# receive socket	
		#------------------------
		#self.my_receive_socket =  posnet_tcp_client.recv_answers_prepare()
		#self.socket_port_number = self.my_receive_socket.getsockname()[1]
		# -> message.answer_type = socket_port_number
		
		# sesions
		#--------------
		self.tunnels = dict()
		self.threads = dict()

	def create_loopback_tunnel(self):
		global peer_node
		tunnel_create_address = self.create_tunnel_first(peer_node.id())
		m = posnet_overlay_messages.posnet_tunnel_message(peer_node.id(),peer_node.id(),tunnel_create_address)
		peer_node.receive(m)
	
	def _tunnel_recv_from(self,tunnel_socket,source_id):
		global peer_node
		try:
			while 1:
				msg = posnet_tcp_client.tunnel_recv_message(tunnel_socket)
				if msg is None:
					continue
				self.logger.info("** receive over tunnel :)")
				try: peer_node.receive(msg)	
				except Exception,e:
					self.logger.error("in message received from tunnel: %s"%str(e))
		except Exception,e:	
			print "Exception during receive message: tunnel closed (%s)"%str(e)
	def _wait_for_tunnel_establishment(self,tunnel_socket_binding,binding_socket,source_id):
		self.logger.info("tunnel end point created, waiting for establishment with %s"%str(source_id))
		try:
			tunnel_socket = posnet_tcp_client.tunnel_wait_for_establishment(tunnel_socket_binding)
			if tunnel_socket is None:
				self.logger.error("tunnel creation with %s canceled"%str(source_id))
				return
			self.tunnels[source_id] = tunnel_socket	
			try:
				self.logger.info("tunnel established with %s: socket=%s"% ( str(source_id), tunnel_socket.getsockname()) )
				self._tunnel_recv_from(tunnel_socket,source_id)	
			finally:
				if self.tunnels.has_key(source_id):
					del self.tunnels[source_id]
				if self.threads.has_key(source_id):
					del self.threads[source_id]
		except Exception,e:
			from traceback import print_exc
			self.logger.error("Tunnel connection %s failure: :%s"%(str(tunnel_socket_binding),str(e)) )
			print_exc() 
		

	def _establish_tunnel(self,host_port,destination_id):
		self.logger.info("connecting to tunnel %s..."%str( ( host_port[0],host_port[1] ) ))
		tunnel_socket = posnet_tcp_client.connect_to_tunnel( host_port[0],host_port[1] )	
		if tunnel_socket is None:
			self.logger.error("connection to tunnel %s impossible"%str( ( host_port[0],host_port[1] ) ) )
			return
		self.tunnels[destination_id] = tunnel_socket
		try:
			self.logger.info("==== tunnel established with %s"%str(destination_id))
			self._tunnel_recv_from(tunnel_socket,destination_id)	
			self.logger.info("==== tunnel %s closed"%str(destination_id))
		finally:
			if self.tunnels.has_key(destination_id):
				del self.tunnels[destination_id]
			if self.threads.has_key(destination_id):
				del self.threads[destination_id]
		

	def create_tunnel_first(self,source_id):
		""" return the port :) """
		if self.tunnels.has_key(source_id) or self.threads.has_key(source_id):
			return None # tunnel already available TODO check alive
		else:
			# create a tunnel	
			# global server
			global peer_node
			tunnel_socket_binding = posnet_tcp_client.allocate_tunnel()
			tunnel_contact_port = tunnel_socket_binding.getsockname()[1]
			tunnel_contact = (peer_node.id()[0],tunnel_contact_port)
			self.logger.info("allocate tunnel %s"%str(tunnel_contact))
			# create listening thread
			t = thread.start_new_thread( self._wait_for_tunnel_establishment, (tunnel_socket_binding,tunnel_contact,source_id) ) 
			self.threads[source_id] = t
			return tunnel_contact

	def connect_tunnel_second(self,host_port,destination_id):
		if self.tunnels.has_key(destination_id):
			return None # tunnel already available TODO check alive
		else:
			# connect to tunnel
			t = thread.start_new_thread( self._establish_tunnel, (host_port,destination_id,) ) 
			self.threads[destination_id] = t
	
	def propagate_message(self,message,answer_type = None,sending_id = None):
		self.logger.info("[%s] %s -> %s"%(str(message.message_type),str(sending_id),add_to_str(message.target),))
		if True and self.tunnels.has_key( message.target ):
			#TODO implement tunnels between nodes	
			# send over tunnel (tcp socket already opened)
			try:
				self.logger.info("propagate into tunnel %s"% add_to_str(message.target))
				return posnet_tcp_client.tunnel_send_message(self.tunnels[message.target], message) 
			except Exception,e:
				self.logger.error("bad propagation into tunnel %s"%str(e)) 
				print_exc()
		else:
			#if message.message_type == posnet_overlay_messages.posnet_message_type_open_link:
			#	existing_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			#	posnet_tcp_client.connect_retry(existing_socket,message.target)
			#	self.add_talking_socket( existing_socket, message.target) 
			return posnet_tcp_client.send_message(message.target,message,answer_type,None,None)

	def __del__(self):
		for t in self.tunnels:
			try: t.close()
			except: pass


# global variable
peer_node = None
server = None
job_executer  = None

import scheduler
import signal
import time
import threading
	
def posnet_server_help_message(out = None):
	import sys
	if out is None: out = sys.stderr
	print >>out,"-"*30
	print >>out,"Create a posnet tcp server" 
	print >>out,"-"*30
	print >>out,"* usage:",sys.argv[0],"port_number [nb_poset_dimensions = 2] | [ posnet_join_node_address posnet_join_node_port]"

if __name__ == "__main__":
	import socket
	socket.setdefaulttimeout(5)
	if len(sys.argv) < 2:
		posnet_server_help_message()
		raise SystemExit
	myHost = socket.gethostbyname(socket.gethostname())
	listen_port = int(sys.argv[1])         
	myID = (myHost,listen_port)
	#------------------
	# posnet node
	Poset = dict()
	if len(sys.argv) == 3:
		try:
			myNbDimensions = int(sys.argv[2])
			for x in xrange(myNbDimensions):
				Poset[str(x)] = [1] 
		except:
			pass
	elif len(sys.argv) == 4:
		ohost = sys.argv[2]
		oport = int(sys.argv[3])
		Poset = posnet_tcp_client.posnet_ping((ohost,oport))	
	else:
		Poset = {"x":range(1,2),"y":range(1,2)} #,"z":range(7)}

	#-----------------
	# Logging 
	# set up logging to file
	logging.basicConfig(level=logging.INFO, #DEBUG,
			    #format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
			    format='%(created)s\t%(name)-12s\t%(levelname)-8s\t%(message)s',
			    datefmt='%m-%d %H:%M:s'  #,
			    #filename='posnet_overlay_node2_'+myHost+'_'+str(listen_port)+'.log',
			    #filemode='w'
				)
	# define a Handler which writes INFO messages or higher to the sys.stderr
	console = logging.StreamHandler()
	console.setLevel(logging.DEBUG) #.INFO)
	formatter = logging.Formatter('%(name)-12s: %(levelname)-3s %(message)s') # set a format which is simpler for console use
	console.setFormatter(formatter) # tell the handler to use this format
	#logging.getLogger('').addHandler(console) # add the handler to the root logger
	
	logger = logging.getLogger('posnet.server')
	#---	
	#*** global server ***
	listen_address = ""
	myaddr = (listen_address,listen_port)
	num_threads = 1
	try:
		logger.info("start server: listening on %s"%add_to_str(myaddr))
		server =  posnet_tcp_server.posnet_tcp_server(myaddr)
		#--
		
		logger.info("start thread scheduler: %d thread(s)"%int(num_threads))
		try:
			job_executer = scheduler.Scheduler("posnet_query_handler")
			job_executer.add_threads(num_threads)
			class queue_instancier:
				def __init__(self,_job_executer):
					self.job_executer = _job_executer
				def enqueue(self, job):
					global job_executer
					def execute_the_job(): self.job_executer(job)
					job_executer.enqueue(execute_the_job)
				def process_queue(self): pass
			#------------------------
			
			#--
			logger.info("create posnet node \"%s\": poset has %d keys"%(str(myID),len(Poset.keys())))
			peer_node = posnet_overlay_node2.posnet_overlay_node(Poset,myID,queue_instancier)
			peer_node.set_underlay( internet_posnet_underlay() )
			peer_node.set_debug(False)	
			#------------------------
			# print Poset :)
			Poset_keys = peer_node.get_poset().keys()
			Poset_keys.sort()
			#logger.info("Poset has %d keys"%len(Poset_keys))
			for k in Poset_keys:
				logger.info("poset key %s: %s"%(str(k[0]),str(k[1])))	
			#---------------------
			# handling messages
			def message_handler(add,buf,client_socket):
				global peer_node
				try:
					message = posnet_overlay_messages.array_to_overlay_message( buf )
					peer_node.receive(message,client_socket)
				except Exception,e:
					print "-"*30
					print_exc()
					print "-"*30
			server.set_protocol_handler(message_handler)
			#----------------
			# catch signal
			def beingkilled(*args):
				global server
				#console.info("signal received: kill scheduler and servers")
				try: server.shutdown()	
				except: pass
			signal.signal(signal.SIGINT,beingkilled)
			#---------
			# Join
			#-------------
			if len(sys.argv) >= 4:
				join_host = sys.argv[2]
				join_address = int(sys.argv[3])
				def send_join_task():
					global peer_node
					time.sleep(1)
					print >>sys.stderr,"send join to node:",(join_host,join_address)
					peer_node.send_join( (join_host,join_address) )
					#
					#server.shutdown()			
				t = threading.Thread(target = send_join_task)
				t.start()
			try:
				logger.info("begin Control Plane/Query Plane service (TCP)")
				### uncomment to profile code
				#import profile
				#print profile.run("server.serve_forever()") 
				### --- and comment next line if 2 previous ones are uncommented
				server.serve_forever()
				logger.info("end Control Plane/Query Plane  service (TCP)")
			finally:
				#job_executer.kill()
				try: server.shutdown()
				except: pass 
		finally:
			job_executer.kill()
	except Exception,e:
		sys.stdout.flush()
		sys.stderr.flush()
		logger.error("%s"%str(e))
		print >>sys.stderr,"*"*15,"traceback of the error","*"*15
		print_exc()
		print >>sys.stderr,"*"*15,"/ end traceback of the error","*"*15
